這整個系列文轉眼間也要進入下半場,在 Day 9 到現在的 Day 18 不小心用了有一些多的篇幅在研究 Vite,其實原本還有 HMR、Module Federation 想研究,但盤點完大綱後覺得畢竟主題中還有 Rust 這回事,還是要拉回來一些。於是想在這裡先打住做個 Vite 系列總整理,後面篇幅還夠的話再來補上。
今天這篇中會根據 Day 9 開頭列的幾個問題一一做個簡短的重點整理,另外也會把前幾天追原始碼的脈絡說明一下。
可以記住這兩張圖的比較:
Vite 利用瀏覽器原生支援 ESM 載入的特性,在冷啟動時直接按需載入需要的模組,因此不需要像以往 bundle-based 的 dev server 一定要走過整個分析依賴與打包的流程。
因為在瀏覽器想要以 ESM 的方式去載入第三方套件時,可能會遇到幾個問題:
為了解決以上三個問題,Vite 在啟動 dev server 前會用 esbuild 快速地將專案中有使用到的第三方套件預先打包到 node_modules/.vite/deps
底下,這就是 pre-bundling。
在 Day 15 的實驗中可以得到一個結論,當今天專案中載入了許多複雜的大型依賴套件,第一次在做 pre-bundle 時仍會稍微花一些時間打包,但時間上仍比大型的 Webpack 專案快上許多。
在有執行過第一次的 pre-bundle 後,打包後的內容會被放在 node_modules/.vite/deps
做快取,因此之後在啟 dev server 時都能在短時間內準備好。
Vite 是 dev server,esbuild 是 bundler。Vite 在啟動 dev server 時會做 pre-bundling,而這個任務就是利用 esbuild 來幫忙快速打包出第三方套件們的 ESM 模組。
這問題也可以換個問法:「為什麼 Vite 要用兩套 bundler?」
在 Day 17 中有講到,esbuild 目前做為 bundler 雖然快但有許多限制:
而在 production build 方面,Rollup 能補足上述的不足,因此最終採用它來做這部份的工作。但 Rollup 也有一些待解決的問題:
對這段說明有興趣也可以參考 Evan You 在 ViteConf 2023 這一段的說明:
上面提到 esbuild 與 Rollup 做為 bundler 有各自的限制與問題。且因為現在 dev 與 production 使用兩套不同的 bundler,有時候開發者會遇到一些只有在 production 才有的問題,這時就會不好做 debug。
因此 Vite 在 2023 年底開始與 Rspack 團隊合作從頭打造一款 Rust-based bunder —— Rolldown,想在 Vite v6 中統一兩種環境的 bundler,且利用 Rust 編譯語言的效能來提升 production build 的速度。
這個後面如果篇幅夠可以再來細講跟做實驗玩玩看,但簡單說明的話 OXC 是一套用 Rust 開發的 JS 工具鏈,裡面分別有實作 parser、linter、transformer 等功能。
而前面提到原本在 Rollup 中用來做為 JS parser 的 Babel 在 v4 時有替換成更快的 SWC (Speedy Web Compiler),而這個 OXC parser 的效能評測上看起來又比 SWC 快上 3 倍,看起來 Vite 團隊打算在打造 Rolldown 的底層時直接用上,可預見一個快上加快的 bundler 正在醞釀中。
HMR (熱模組更新) 簡單說明的話就是指在開發過程中,在檔案變更後,dev server 可以在不重整頁面的情況下,就直接幫你局部替換掉畫面上為新的模組。
最常拿來比較的對象是 Live Reload,兩者的差異在於 Live Reload 會在檔案變更後去重整頁面,但這樣的缺點是可能其他沒被更動的模組的狀態也會因為頁面被刷新而被重置,在開發上較不方便。
至於 HMR 的原理的話前面在 Day 13 中有追到一個叫做 Chokidar 的套件會幫忙做檔案異動的監聽,在接到檔案異動的事件後會去觸發的這個 onHMRUpdate
應該就是原理所在,一樣也是後面篇幅還夠時再來繼續研究。
前面也花了些篇幅來嘗試追看看開源專案原始碼,一路從啟 dev server 到執行 pre-bundling 完成可以整理如下:
cli.alias('dev')
createServer
connect
套件建立 middlewaresChokidar
套件來做為 watcher 監聽檔案變化 (HMR 主要邏輯所在)createServer
initServer
environment.depsOptimizer?.init()
depsOptimizer
中有兩種環境與設定條件:
createExplicitDepsOptimizer
createDepsOptimizer
createDepsOptimizer
loadCachedDepOptimizationMetadata
discoverProjectDependencies
scanImports
runOptimizeDeps
prepareEsbuildOptimizerRun
esbuild.context.rebuild
node_modules/.vite/deps
底下也把上述的探索路徑做成圖示版:
而關於 Vite 原始碼這只算是冰山一角,其他也還有 HMR、Module Graph、plugin、npm run build
等沒追,對這部份有興趣的讀者歡迎參考以上追過的筆記,也強烈推薦搭配菜市場阿龍的《Vite 原始碼解讀》影片版可能更好理解。
今天將前面提到的 Vite 的幾個問題做了總整理,也把這幾天以來追的 Vite 原始碼做成了完整版的圖示,也算是一個 Vite 的精彩完結篇,如果有什麼問題或講錯的地方都在麻煩留言給我,感謝你的閱讀!
從明天開始就要正式進入「Rust 的戰國時代」的部份了,我們明天見!